{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Toolkits 활용 Agent\n",
    "\n",
    "LangChain 프레임워크를 사용하는 가장 큰 이점은 3rd-party integration 되어 있는 다양한 기능들입니다.\n",
    "\n",
    "그 중 Toolkits 는 다양한 도구를 통합하여 제공합니다.\n",
    "\n",
    "아래 링크에서 다양한 Tools/Toolkits 를 확인할 수 있습니다.\n",
    "\n",
    "**참고**\n",
    "\n",
    "- [Agent Toolkits](https://api.python.langchain.com/en/latest/community/agent_toolkits.html)\n",
    "\n",
    "- [Tools](https://python.langchain.com/docs/integrations/tools/)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# API 키를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API 키 정보 로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH15-Agent-Projects\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "먼저, 임시 폴더인 `tmp` 를 생성합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "if not os.path.exists(\"tmp\"):\n",
    "    os.mkdir(\"tmp\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## FileManagementToolkit\n",
    "\n",
    "`FileManagementToolkit` 는 로컬 파일 관리를 위한 도구 모음입니다. \n",
    "\n",
    "### 주요 구성 요소\n",
    "\n",
    "**파일 관리 도구들**\n",
    "\n",
    "- `CopyFileTool`: 파일 복사\n",
    "  \n",
    "- `DeleteFileTool`: 파일 삭제\n",
    "\n",
    "- `FileSearchTool`: 파일 검색\n",
    "\n",
    "- `MoveFileTool`: 파일 이동\n",
    "\n",
    "- `ReadFileTool`: 파일 읽기\n",
    "\n",
    "- `WriteFileTool`: 파일 쓰기\n",
    "\n",
    "- `ListDirectoryTool`: 디렉토리 목록 조회\n",
    "\n",
    "**설정**\n",
    "\n",
    "- `root_dir`: 파일 작업의 루트 디렉토리 설정 가능\n",
    "\n",
    "- `selected_tools`: 특정 도구만 선택적으로 사용 가능\n",
    "\n",
    "\n",
    "**동적 도구 생성**\n",
    "\n",
    "- `get_tools` 메서드로 선택된 도구들의 인스턴스 생성\n",
    "\n",
    "\n",
    "이 `FileManagementToolkit`은 로컬 파일 관리 작업을 자동화하거나 AI 에이전트에게 파일 조작 능력을 부여할 때 유용하게 사용할 수 있습니다. 단, 보안 측면에서 신중한 접근이 필요합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# FileManagementToolkit을 가져옵니다. 이 도구는 파일 관리 작업을 수행하는 데 사용됩니다.\n",
    "from langchain_community.agent_toolkits import FileManagementToolkit\n",
    "\n",
    "# 'tmp'라는 이름의 디렉토리를 작업 디렉토리로 설정합니다.\n",
    "working_directory = \"tmp\"\n",
    "\n",
    "# FileManagementToolkit 객체를 생성합니다.\n",
    "# root_dir 매개변수에 작업 디렉토리를 지정하여 모든 파일 작업이 이 디렉토리 내에서 이루어지도록 합니다.\n",
    "toolkit = FileManagementToolkit(root_dir=str(working_directory))\n",
    "\n",
    "# toolkit.get_tools() 메서드를 호출하여 사용 가능한 모든 파일 관리 도구를 가져옵니다.\n",
    "# 이 도구들은 파일 복사, 삭제, 검색, 이동, 읽기, 쓰기, 디렉토리 목록 조회 등의 기능을 제공합니다.\n",
    "available_tools = toolkit.get_tools()\n",
    "\n",
    "# 사용 가능한 도구들의 이름을 출력합니다.\n",
    "print(\"[사용 가능한 파일 관리 도구들]\")\n",
    "for tool in available_tools:\n",
    "    print(f\"- {tool.name}: {tool.description}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 도구 중 일부만 지정하여 선택하는 것도 가능합니다\n",
    "tools = FileManagementToolkit(\n",
    "    root_dir=str(working_directory),\n",
    "    selected_tools=[\"read_file\", \"file_delete\", \"write_file\", \"list_directory\"],\n",
    ").get_tools()\n",
    "tools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "read_tool, delete_tool, write_tool, list_tool = tools\n",
    "\n",
    "# 파일 쓰기\n",
    "write_tool.invoke({\"file_path\": \"example.txt\", \"text\": \"Hello World!\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 파일 목록 조회\n",
    "print(list_tool.invoke({}))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 파일 삭제\n",
    "print(delete_tool.invoke({\"file_path\": \"example.txt\"}))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 파일 목록 조회\n",
    "print(list_tool.invoke({}))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 필요한 모듈과 클래스를 임포트합니다.\n",
    "from langchain.tools import tool\n",
    "from typing import List, Dict\n",
    "from langchain_teddynote.tools import GoogleNews\n",
    "\n",
    "\n",
    "# 최신 뉴스 검색 도구를 정의합니다.\n",
    "@tool\n",
    "def latest_news(k: int = 5) -> List[Dict[str, str]]:\n",
    "    \"\"\"Look up latest news\"\"\"\n",
    "    # GoogleNews 객체를 생성합니다.\n",
    "    news_tool = GoogleNews()\n",
    "    # 최신 뉴스를 검색하고 결과를 반환합니다. k는 반환할 뉴스 항목의 수입니다.\n",
    "    return news_tool.search_latest(k=k)\n",
    "\n",
    "\n",
    "# FileManagementToolkit을 사용하여 파일 관리 도구들을 가져옵니다.\n",
    "tools = FileManagementToolkit(\n",
    "    root_dir=str(working_directory),\n",
    ").get_tools()\n",
    "\n",
    "# 최신 뉴스 검색 도구를 tools 리스트에 추가합니다.\n",
    "tools.append(latest_news)\n",
    "\n",
    "# 모든 도구들이 포함된 tools 리스트를 출력합니다.\n",
    "tools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.agents import create_tool_calling_agent, AgentExecutor\n",
    "from langchain_community.chat_message_histories import ChatMessageHistory\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
    "from langchain_teddynote.messages import AgentStreamParser\n",
    "\n",
    "# session_id 를 저장할 딕셔너리 생성\n",
    "store = {}\n",
    "\n",
    "# 프롬프트 생성\n",
    "# 프롬프트는 에이전트에게 모델이 수행할 작업을 설명하는 텍스트를 제공합니다. (도구의 이름과 역할을 입력)\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"You are a helpful assistant. \"\n",
    "            \"Make sure to use the `latest_news` tool to find latest news. \"\n",
    "            \"Make sure to use the `file_management` tool to manage files. \",\n",
    "        ),\n",
    "        (\"placeholder\", \"{chat_history}\"),\n",
    "        (\"human\", \"{input}\"),\n",
    "        (\"placeholder\", \"{agent_scratchpad}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# LLM 생성\n",
    "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
    "\n",
    "# Agent 생성\n",
    "agent = create_tool_calling_agent(llm, tools, prompt)\n",
    "\n",
    "# AgentExecutor 생성\n",
    "agent_executor = AgentExecutor(\n",
    "    agent=agent,\n",
    "    tools=tools,\n",
    "    verbose=False,\n",
    "    handle_parsing_errors=True,\n",
    ")\n",
    "\n",
    "\n",
    "# session_id 를 기반으로 세션 기록을 가져오는 함수\n",
    "def get_session_history(session_ids):\n",
    "    if session_ids not in store:  # session_id 가 store에 없는 경우\n",
    "        # 새로운 ChatMessageHistory 객체를 생성하여 store에 저장\n",
    "        store[session_ids] = ChatMessageHistory()\n",
    "    return store[session_ids]  # 해당 세션 ID에 대한 세션 기록 반환\n",
    "\n",
    "\n",
    "# 채팅 메시지 기록이 추가된 에이전트를 생성합니다.\n",
    "agent_with_chat_history = RunnableWithMessageHistory(\n",
    "    agent_executor,\n",
    "    # 대화 session_id\n",
    "    get_session_history,\n",
    "    # 프롬프트의 질문이 입력되는 key: \"input\"\n",
    "    input_messages_key=\"input\",\n",
    "    # 프롬프트의 메시지가 입력되는 key: \"chat_history\"\n",
    "    history_messages_key=\"chat_history\",\n",
    ")\n",
    "\n",
    "agent_stream_parser = AgentStreamParser()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = agent_with_chat_history.stream(\n",
    "    {\n",
    "        \"input\": \"최신 뉴스 5개를 검색하고, 각 뉴스의 제목을 파일명으로 가지는 파일을 생성하고(.txt), \"\n",
    "        \"파일의 내용은 뉴스의 내용과 url을 추가하세요. \"\n",
    "    },\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")\n",
    "\n",
    "print(\"Agent 실행 결과:\")\n",
    "for step in result:\n",
    "    agent_stream_parser.process_agent_steps(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`tmp` 폴더 내부를 확인해보면 아래와 같이 파일이 생성된 것을 확인할 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](./assets/toolkits-01.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = agent_with_chat_history.stream(\n",
    "    {\n",
    "        \"input\": \"이전에 생성한 파일 제목 맨 앞에 제목에 어울리는 emoji를 추가하여 파일명을 변경하세요. \"\n",
    "        \"파일명도 깔끔하게 변경하세요. \"\n",
    "    },\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")\n",
    "\n",
    "print(\"Agent 실행 결과:\")\n",
    "for step in result:\n",
    "    agent_stream_parser.process_agent_steps(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`tmp` 폴더 내부를 확인해보면 아래와 같이 파일명이 변경된 것을 확인할 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](./assets/toolkits-02.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = agent_with_chat_history.stream(\n",
    "    {\n",
    "        \"input\": \"이전에 생성한 모든 파일을 `news` 폴더를 생성한 뒤 해당 폴더에 모든 파일을 복사하세요. \"\n",
    "        \"내용도 동일하게 복사하세요. \"\n",
    "    },\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")\n",
    "\n",
    "print(\"Agent 실행 결과:\")\n",
    "for step in result:\n",
    "    agent_stream_parser.process_agent_steps(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`tmp` 폴더 내부를 확인해보면 아래와 같이 `news` 폴더가 생성되고 파일이 복사된 것을 확인할 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](./assets/toolkits-03.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = agent_with_chat_history.stream(\n",
    "    {\"input\": \"news 폴더를 제외한 모든 .txt 파일을 삭제하세요.\"},\n",
    "    config={\"configurable\": {\"session_id\": \"abc123\"}},\n",
    ")\n",
    "\n",
    "print(\"Agent 실행 결과:\")\n",
    "for step in result:\n",
    "    agent_stream_parser.process_agent_steps(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`tmp` 폴더 내부를 확인해보면 아래와 같이 `news` 폴더를 제외한 모든 파일이 삭제된 것을 확인할 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](./assets/toolkits-04.png)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
